package cn.enn.smart.filter;

import cn.enn.smart.exception.ValidateCodeException;
import cn.enn.smart.service.ValidateCodeService;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

public class ValidateCodeFilter extends OncePerRequestFilter {

	private final Set<String> processUrls;

	private boolean postOnly = true;

	private String validateCodeParameter = "code";

	private String validateCodeUniqueKey = "uniqueKey";

	private Set<String> loginParameter;

	private final AntPathMatcher pathMatcher = new AntPathMatcher();

	private static final Logger LOGGER = LoggerFactory.getLogger(ValidateCodeFilter.class);

	private ValidateCodeService validateCodeService;

	private final AuthenticationFailureHandler authenticationFailureHandler;

	public ValidateCodeFilter(@NonNull Set<String> processUrls,
			@NonNull AuthenticationFailureHandler authenticationFailureHandler) {
		this.processUrls = processUrls;
		this.authenticationFailureHandler = authenticationFailureHandler;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		final boolean debug = LOGGER.isDebugEnabled();
		String uri = request.getRequestURI();
		if (supportValidateCode(uri)) {
			try {

				if (postOnly && !request.getMethod().equals("POST")) {
					throw new ValidateCodeException(
							"ValidateCode method not supported: " + request.getMethod() + "only support POST");
				}

				String validateCode = obtainValidateCode(request);
				if (StringUtils.isBlank(validateCode)) {
					throw new ValidateCodeException("ValidateCode parameter [" + validateCodeParameter + "] not find");
				}

				String uniqueKey = obtainUniqueKey(request);
				if (StringUtils.isBlank(uniqueKey)) {
					throw new ValidateCodeException("ValidateCode parameter [" + validateCodeUniqueKey + "] not find");
				}

				String storeCode = validateCodeService.loadValidateByUniqueKey(uniqueKey);
				if (!StringUtils.equalsIgnoreCase(storeCode.trim(), validateCode.trim())) {
					throw new ValidateCodeException("ValidateCode is invalid");
				}

				response.addHeader("Content-Type", "application/json");
				response.setCharacterEncoding(StandardCharsets.UTF_8.name());
				response.getWriter().write("验证码正确");
				return;
			} catch (AuthenticationException failed) {
				SecurityContextHolder.clearContext();

				if (debug) {
					this.logger.debug("Authentication request for failed!", failed);
				}

				authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
				return;
			}
		}
		filterChain.doFilter(request, response);
	}

	private String obtainValidateCode(HttpServletRequest request) {
		return request.getParameter(validateCodeParameter);
	}

	private String obtainUniqueKey(HttpServletRequest request) {
		return request.getParameter(validateCodeUniqueKey);
	}

	private boolean supportValidateCode(String uri) {
		for (String processUrl : processUrls) {
			if (pathMatcher.match(processUrl, uri)) {
				return true;
			}
		}
		return false;
	}

	public String getValidateCodeParameter() {
		return validateCodeParameter;
	}

	public void setValidateCodeParameter(String validateCodeParameter) {
		this.validateCodeParameter = validateCodeParameter;
	}

	public ValidateCodeService getValidateCodeService() {
		return validateCodeService;
	}

	public void setValidateCodeService(ValidateCodeService validateCodeService) {
		this.validateCodeService = validateCodeService;
	}

	public String getValidateCodeUniqueKey() {
		return validateCodeUniqueKey;
	}

	public void setValidateCodeUniqueKey(String validateCodeUniqueKey) {
		this.validateCodeUniqueKey = validateCodeUniqueKey;
	}

	public boolean isPostOnly() {
		return postOnly;
	}

	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	public Set<String> getLoginParameter() {
		return loginParameter;
	}

	public void setLoginParameter(Set<String> loginParameter) {
		this.loginParameter = loginParameter;
	}
}
